package com.tongfusoft.ldap;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;

import com.tongfusoft.utils.Logger;

/**
 * LDAP 操作的实用类
 * 
 * @author cyj
 * 
 */
public class LdapSession {

	private String providerUrl;
	private String securityAuthentication;
	private String securityPrincipal;
	private String securityCredentials;

	public static interface AttributesMapper<T> {

		public T createObject(Attributes attributes);
	}

	public LdapSession(String providerUrl, String securityAuthentication,
			String securityPrincipal, String securityCredentials) {
		this.providerUrl = providerUrl;
		this.securityAuthentication = securityAuthentication;
		this.securityPrincipal = securityPrincipal;
		this.securityCredentials = securityCredentials;
	}

	public LdapSession() {
	}

	public LdapContext connectLdapServer() throws NamingException {
		Logger.debug("InitialDirContext");
		Logger.debug("INITIAL_CONTEXT_FACTORY: {0}",
				"com.sun.jndi.ldap.LdapCtxFactory");
		Logger.debug("PROVIDER_URL: {0}", providerUrl);
		Logger.debug("SECURITY_AUTHENTICATION: {0}", securityAuthentication);
		Logger.debug("SECURITY_PRINCIPAL: {0}", securityPrincipal);
		Logger.debug("SECURITY_CREDENTIALS: {0}", securityCredentials);
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY,
				"com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, this.providerUrl);
		env.put(Context.SECURITY_AUTHENTICATION, this.securityAuthentication);
		env.put(Context.SECURITY_PRINCIPAL, this.securityPrincipal);
		env.put(Context.SECURITY_CREDENTIALS, this.securityCredentials);
		return new InitialLdapContext(env, null);
	}

	public <T> List<T> search(String searchBase, String searchFilter,
			AttributesMapper<T> mapper) throws NamingException {
		return search(searchBase, searchFilter, 10000, 1, mapper);
	}

	public <T> List<T> search(String searchBase, String searchFilter,
			int pageSize, int page, AttributesMapper<T> mapper)
			throws NamingException {
		SearchControls constraints = new SearchControls();
		constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
		Logger.debug("Search Base: {0}", searchBase);
		Logger.debug("Search Filter: {0}", searchFilter);
		List list = new ArrayList();
		LdapContext ctx = null;
		NamingEnumeration results = null;
		try {
			ctx = connectLdapServer();
			byte[] cookie = null;
			Control control = null;
			while (page > 0) {
				if (cookie != null)
					control = new PagedResultsControl(pageSize, cookie,
							Control.CRITICAL);
				else
					control = new PagedResultsControl(pageSize,
							Control.CRITICAL);
				ctx.setRequestControls(new Control[] { control });
				results = ctx.search(searchBase, searchFilter, constraints);
				while (results != null && results.hasMore()) {
					SearchResult searchResult = (SearchResult) results.next();
					Attributes attributes = searchResult.getAttributes();
					if (page == 1) {// last page
						T o = mapper.createObject(attributes);
						if (o != null)
							list.add(o);
					}
				}
				Control[] controls = ctx.getResponseControls();
				if (controls != null) {
					for (int i = 0; i < controls.length; i++) {
						if (controls[i] instanceof PagedResultsResponseControl) {
							PagedResultsResponseControl prrc = (PagedResultsResponseControl) controls[i];
							cookie = prrc.getCookie();
						}
					}
				}
				page--;
			}
		} catch (NamingException ne) {
			throw ne;
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (results != null)
				try {
					results.close();
				} catch (NamingException ne) {
				}
			if (ctx != null)
				try {
					ctx.close();
				} catch (NamingException ne) {
				}
		}
		Logger.debug("Search Results Count: {0}", list.size());
		return list;
	}

	public void updateAttribute(String name, String field, String value)
			throws NamingException {

		DirContext ctx = null;
		try {
			ctx = connectLdapServer();
			ModificationItem modificationItem[] = new ModificationItem[1];
			modificationItem[0] = new ModificationItem(
					DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(field,
							value));
			ctx.modifyAttributes(name, modificationItem);
			return;
		} catch (NamingException ne) {
			throw ne;
		} finally {
			if (ctx != null)
				try {
					ctx.close();
				} catch (NamingException ne) {
				}
		}
	}

	public void addAttribute(String name, String field, String value)
			throws NamingException {

		DirContext ctx = null;
		try {
			ctx = connectLdapServer();
			ModificationItem modificationItem[] = new ModificationItem[1];
			modificationItem[0] = new ModificationItem(
					DirContext.ADD_ATTRIBUTE, new BasicAttribute(field,
							value));
			ctx.modifyAttributes(name, modificationItem);
			return;
		} catch (NamingException ne) {
			throw ne;
		} finally {
			if (ctx != null)
				try {
					ctx.close();
				} catch (NamingException ne) {
				}
		}
	}

	public void removeAttribute(String name, String field, String value)
			throws NamingException {

		DirContext ctx = null;
		try {
			ctx = connectLdapServer();
			ModificationItem modificationItem[] = new ModificationItem[1];
			modificationItem[0] = new ModificationItem(
					DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(field,
							value));
			ctx.modifyAttributes(name, modificationItem);
			return;
		} catch (NamingException ne) {
			throw ne;
		} finally {
			if (ctx != null)
				try {
					ctx.close();
				} catch (NamingException ne) {
				}
		}
	}

	public void createSubcontext(String name, Attributes attributes)
			throws NamingException {
		DirContext ctx = null;
		try {
			ctx = connectLdapServer();
			ctx.createSubcontext(name, attributes);
		} catch (NamingException ne) {
			throw ne;
		} finally {
			if (ctx != null)
				try {
					ctx.close();
				} catch (NamingException ne) {
				}
		}
	}

	public void deleteSubcontext(String name) throws NamingException {
		DirContext ctx = null;
		try {
			ctx = connectLdapServer();
			ctx.destroySubcontext(name);
		} catch (NamingException ne) {
			throw ne;
		} finally {
			if (ctx != null)
				try {
					ctx.close();
				} catch (NamingException ne) {
				}
		}
	}

	public void setProviderUrl(String providerUrl) {
		this.providerUrl = providerUrl;
	}

	public String getProviderUrl() {
		return providerUrl;
	}

	public void setSecurityAuthentication(String securityAuthentication) {
		this.securityAuthentication = securityAuthentication;
	}

	public String getSecurityAuthentication() {
		return securityAuthentication;
	}

	public void setSecurityPrincipal(String securityPrincipal) {
		this.securityPrincipal = securityPrincipal;
	}

	public String getSecurityPrincipal() {
		return securityPrincipal;
	}

	public void setSecurityCredentials(String securityCredentials) {
		this.securityCredentials = securityCredentials;
	}

	public String getSecurityCredentials() {
		return securityCredentials;
	}
}
